Creating new pipeline using seurat v4.0.2 available 2021.06.08

Load libraries required for Seuratv4

knitr::opts_knit$set(root.dir = "~/Desktop/10XGenomicsData/msAggr_scRNASeq/")
library(dplyr)
library(Seurat)
library(patchwork)
library(ggplot2)
# library(clustree)

store session info

# sink("CMP-v1.20210616.txt")
sessionInfo()
# sink()

Set global variables

projectName <- "CMP"
jackstraw.dim <- 40
source("msAggr_AnalysisCode/read_10XGenomics_data.R")
source("msAggr_AnalysisCode/PercentVariance.R")
source("msAggr_AnalysisCode/Mouse2Human_idconversion.R")
setwd("../cellRanger/") # temporarily changing wd only works if you run the entire chunk at once
data_file.list <- read_10XGenomics_data(sample.list = "CMPm2")
object.data <-Read10X(data_file.list)
cmp.object<- CreateSeuratObject(counts = object.data, min.cells = 3, min.genes = 200, project = projectName)

Clean up to free memory

remove(object.data)

Add mitochondrial metadata and plot some basic features

cmp.object[["percent.mt"]] <- PercentageFeatureSet(cmp.object, pattern = "^mt-")
VlnPlot(cmp.object, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3, pt.size = 0, fill.by = 'orig.ident', )
plot1 <- FeatureScatter(cmp.object, feature1 = "nCount_RNA", feature2 = "percent.mt", group.by = "orig.ident", pt.size = 0.01)
plot2 <- FeatureScatter(cmp.object, feature1 = "nCount_RNA", feature2 = "nFeature_RNA", group.by = "orig.ident", pt.size = 0.01)
plot1 + plot2

We don’t have to worry about comparing library depths, so we’ll just do normalization/Scale data

remove low quality cells require: nFeature_RNA between 200 and 4000 (inclusive) require: percent.mt <=5

print(paste("original object:", nrow(cmp.object@meta.data), "cells", sep = " "))
cmp.object <- subset(cmp.object, 
                                                subset = nFeature_RNA >=200 & 
                                                    nFeature_RNA <= 4000 & 
                                                    percent.mt <= 5
                                                )
print(paste("new object:", nrow(cmp.object@meta.data), "cells", sep = " "))
cmp.object <- NormalizeData(cmp.object, normalization.method = "LogNormalize", scale.factor = 10000)

Find variable features

cmp.object <- FindVariableFeatures(cmp.object, selection.method = "vst", nfeatures = 2000)
top10 <- head(VariableFeatures(cmp.object), 10)
plot1 <- VariableFeaturePlot(cmp.object)
plot2 <- LabelPoints(plot = plot1, points = top10, repel = TRUE)
plot1 + plot2

Scale data (linear transformation)

Save raw object

saveRDS(cmp.object, file = paste0(projectName, "_raw.RDS"))
cmp.object <- RunPCA(cmp.object, features = VariableFeatures(cmp.object), ndims.print = 1:5, nfeatures.print = 5)
DimPlot(cmp.object, reduction = "pca", group.by = "orig.ident")
VizDimLoadings(cmp.object, dims = 1:6, nfeatures = 10, reduction = "pca", ncol = 3)

Calculate dimensionality

ElbowPlot(cmp.object, ndims = 50)
percent.variance(cmp.object@reductions$pca@stdev)

Number of PCs describing X% of variance

tot.var <- percent.variance(cmp.object@reductions$pca@stdev, plot.var = FALSE, return.val = TRUE)
paste0("Num pcs for 80% variance:", length(which(cumsum(tot.var) <= 80)))
paste0("Num pcs for 85% variance:", length(which(cumsum(tot.var) <= 85)))
paste0("Num pcs for 90% variance:", length(which(cumsum(tot.var) <= 90)))
paste0("Num pcs for 95% variance:", length(which(cumsum(tot.var) <= 95)))

Add cluster IDs from Seurat v1

Exported cell IDs for clusters 3, 17, 10, 11 from Seurat v1. Will add these IDs as a metadata column.
Create column “clust.ID” and populate with 0’s. Then import IDs for clusters

clust3.cells <- read.table(file = "Seuratv1_clusterCellIDs/cluster3cellIDs.txt", col.names = "clust03")
clust3.cells <- sapply(clust3.cells, function(x) paste0(gsub("CMP", "CMPm2", x), "-1"))
clust17.cells <- read.table(file = "Seuratv1_clusterCellIDs/cluster17cellIDs.txt", col.names = "clust17")
clust17.cells <- sapply(clust17.cells, function(x) paste0(gsub("CMP", "CMPm2", x), "-1"))
clust10.cells <- read.table(file = "Seuratv1_clusterCellIDs/cluster10cellIDs.txt", col.names = "clust10")
clust10.cells <- sapply(clust10.cells, function(x) paste0(gsub("CMP", "CMPm2", x), "-1"))
clust11.cells <- read.table(file = "Seuratv1_clusterCellIDs/cluster11cellIDs.txt", col.names = "clust11")
clust11.cells <- sapply(clust11.cells, function(x) paste0(gsub("CMP", "CMPm2", x), "-1"))

Add new metadata column

cmp.object@meta.data['clust.ID'] <- 0
head(cmp.object@meta.data)

now map new ids

cmp.object@meta.data$clust.ID[rownames(cmp.object@meta.data) %in% clust3.cells] <- 3
cmp.object@meta.data$clust.ID[rownames(cmp.object@meta.data) %in% clust17.cells] <- 17
cmp.object@meta.data$clust.ID[rownames(cmp.object@meta.data) %in% clust10.cells] <- 10
cmp.object@meta.data$clust.ID[rownames(cmp.object@meta.data) %in% clust11.cells] <- 11

do numbers make sense?

nrow(cmp.object@meta.data[cmp.object@meta.data$clust.ID == 10,])
nrow(cmp.object@meta.data[cmp.object@meta.data$clust.ID == 11,])
nrow(cmp.object@meta.data[cmp.object@meta.data$clust.ID == 17,])
nrow(cmp.object@meta.data[cmp.object@meta.data$clust.ID == 3,])

Color palette

color.palette <- c(
    "coral",
    "chartreuse4",
    "goldenrod1",
    "cadetblue1",
    "burlywood",
    "brown",
    "brown1",
    "blue",
    "blue4",
    "azure3",
    "aquamarine",
    "antiquewhite",
    "cadetblue",
    "gold3",
    "black",
    "darkgreen",
    "deeppink",
    "darkviolet",
    "darkturquoise",
    "darkslategray",
    "darksalmon",
    "darkorchid1",
    "darkolivegreen2",
    "forestgreen",
    "dodgerblue",
    "green",
    "lightpink",
    "lightcoral",
    "khaki1",
    "maroon",
    "peru",
    "lightseagreen",
    "lightsalmon",
    "plum",
    "moccasin",
    "tan",
    "tan1", 
    "red", 
    "purple",
    "khaki4",
    "black", 
    "plum4"
)

Total var 90%

Neighborhood and umap

set total.var <- 90%

color.palette <- c(
    "coral",
    "chartreuse4",
    "goldenrod1",
    "cadetblue1",
    "burlywood",
    "brown",
    "brown1",
    "blue",
    "blue4",
    "azure3",
    "aquamarine",
    "antiquewhite",
    "cadetblue",
    "gold3",
    "black",
    "darkgreen",
    "deeppink",
    "darkviolet",
    "darkturquoise",
    "darkslategray",
    "darksalmon",
    "darkorchid1",
    "darkolivegreen2",
    "forestgreen",
    "dodgerblue",
    "green",
    "lightpink",
    "lightcoral",
    "khaki1",
    "maroon",
    "peru",
    "lightseagreen",
    "lightsalmon",
    "plum",
    "moccasin",
    "tan",
    "tan1", 
    "red", 
    "purple",
    "khaki4",
    "black", 
    "plum4"
)

Plot UMAP

cmp.object <- FindNeighbors(cmp.object, dims = 1:ndims)
Computing nearest neighbor graph
Computing SNN
cmp.object <- FindNeighbors(cmp.object, dims = 1:ndims)
Computing nearest neighbor graph
Computing SNN
cmp.object <- FindClusters(cmp.object, resolution = 0.5)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 12059
Number of edges: 452999

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8502
Number of communities: 9
Elapsed time: 1 seconds
cmp.object <- RunUMAP(cmp.object, dims = 1: ndims)
Warning: The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
This message will be shown once per session
15:09:54 UMAP embedding parameters a = 0.9922 b = 1.112
15:09:54 Read 12059 rows and found 33 numeric columns
15:09:54 Using Annoy for neighbor search, n_neighbors = 30
15:09:54 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:09:55 Writing NN index file to temp file /var/folders/4f/fwrj6fnn1dn4g8wsf0zv563hjsvl24/T//RtmpYaUTrq/file158c04b44b74a
15:09:55 Searching Annoy index using 1 thread, search_k = 3000
15:09:58 Annoy recall = 100%
15:09:59 Commencing smooth kNN distance calibration using 1 thread
15:09:59 Initializing from normalized Laplacian + noise
15:10:00 Commencing optimization for 200 epochs, with 513294 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:10:06 Optimization finished
for(x in c(0.5, 1, 1.5, 2, 2.5)){
    cmp.object <- FindClusters(cmp.object, resolution = x)
}
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 12059
Number of edges: 452999

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8502
Number of communities: 9
Elapsed time: 1 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 12059
Number of edges: 452999

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.7974
Number of communities: 17
Elapsed time: 1 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 12059
Number of edges: 452999

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.7652
Number of communities: 22
Elapsed time: 1 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 12059
Number of edges: 452999

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.7404
Number of communities: 27
Elapsed time: 1 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 12059
Number of edges: 452999

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.7189
Number of communities: 30
Elapsed time: 1 seconds

for each resolution, number/percentage of cells in each cluster?

for (meta.col in colnames(cmp.object@meta.data)){
    if(grepl(pattern = ("RNA_snn_res"), x = meta.col)==TRUE){
        myplot <- DimPlot(cmp.object, 
                                            group.by = meta.col,
                                            reduction = "umap", 
                                            cols = color.palette
                                            ) + 
            ggtitle(paste0(projectName, " dim", ndims, "res", gsub("RNA_snn_res", "", meta.col) ))
        plot(myplot)
    }
}

For each resolution, what percentage of cells in each cluster are enriched for one of our clust.IDs?

Test: what percentage of each new clusterID matches one of the older clusters?

tot.cells <- nrow(cmp.object@meta.data)
for (meta.col in colnames(cmp.object@meta.data)){
    if(grepl(pattern = ("RNA_snn_res"), x = meta.col)==TRUE){
        new.clusters <- sort(as.numeric(levels(cmp.object@meta.data[[meta.col]])))
        stats.df <- data.frame(matrix(ncol = 2, nrow = length(new.clusters)))
        colnames(stats.df) <- c("num_cells", "pct_pop")
        rownames(stats.df) <- new.clusters
        meta.df <- cmp.object@meta.data
        for(row.id in rownames(stats.df)){
                num.x <- nrow(meta.df[meta.df[meta.col] == row.id,])
                pct.x <- as.integer(num.x / tot.cells *100)
                # print(pct.x)
                stats.df[row.id, "num_cells"] <- num.x
                stats.df[row.id, "pct_pop"] <- pct.x
        }
        print(stats.df)
    }
}

Absolutely terrible overlap, no enrichment of any of these across the new clustering algorithm. Maybe should try 95% variation covered

Find old cells on UMAP

time for the super scarey moment to see if the cells from seuratv1 still cluster together on in seurat v4

DimPlot(cmp.object,
                reduction = "umap",
                group.by = "clust.ID", 
                # split.by = "orig.ident",
                cols = c("gray", "orange", "blue", "red", "green"),)
DimPlot(cmp.object,
                reduction = "umap",
                group.by = "orig.ident", 
                split.by = "clust.ID",
                cols = c("gray", "orange", "blue", "red", "green"),)

Total var 95%

Neighborhood and umap

set total.var <- 95%

tot.var <- percent.variance(cmp.object@reductions$pca@stdev, plot.var = FALSE, return.val = TRUE)
ndims <- length(which(cumsum(tot.var) <= 95))

cmp.object <- FindNeighbors(cmp.object, dims = 1:ndims)
cmp.object <- FindClusters(cmp.object, resolution = 0.5)
cmp.object <- RunUMAP(cmp.object, dims = 1: ndims)

saveRDS(cmp.object, file = paste0(projectName, "_dim", ndims, ".RDS"))

Plot UMAP

for(x in c(0.5, 1, 1.5, 2, 2.5)){
    cmp.object <- FindClusters(cmp.object, resolution = x)
}

For each resolution, what percentage of cells in each cluster are enriched for one of our clust.IDs?

Test: what percentage of each new clusterID matches one of the older clusters?

for (meta.col in colnames(cmp.object@meta.data)){
    if(grepl(pattern = ("RNA_snn_res"), x = meta.col)==TRUE){
        new.clusters <- sort(as.numeric(levels(cmp.object@meta.data[[meta.col]])))
        enrich.df <- data.frame(matrix(ncol = 4, nrow = length(new.clusters)))
        colnames(enrich.df) <- c(3, 17, 10, 11)
        rownames(enrich.df) <- new.clusters
        meta.df <- cmp.object@meta.data
        for(row.id in rownames(enrich.df)){
            tot.clus <- nrow(meta.df[meta.df[[meta.col]] == row.id,])
            for(col.id in colnames(enrich.df)){
                num.x <- nrow(meta.df[(meta.df[[meta.col]] == row.id) & (meta.df$clust.ID == col.id),])
                pct.x <- as.integer(num.x / tot.clus *100)
                # print(pct.x)
                enrich.df[row.id, col.id] <- pct.x
            }
        }
        colnames(enrich.df) <- sapply(colnames(enrich.df), function(x) paste0("oldcluster", x))
        rownames(enrich.df) <- sapply(rownames(enrich.df), function(x) paste0("newcluster", x))
        xlsx::write.xlsx(enrich.df, file = paste0("PctOfNewClustersOverlappingOldClusters_", projectName, "_dim", ndims, ".xlsx"), sheetName = paste0(gsub("RNA_snn_", "", meta.col)), append = TRUE)
        print(enrich.df)
    }
}

Absolutely terrible overlap, no enrichment of any of these across the new clustering algorithm. Maybe should try 95% variation covered

Find old cells on UMAP

time for the super scarey moment to see if the cells from seuratv1 still cluster together on in seurat v4

DimPlot(cmp.object,
                reduction = "umap",
                group.by = "clust.ID", 
                pt.size = .1,
                # split.by = "orig.ident",
                cols = c("gray", "orange", "blue", "red", "green"),)
DimPlot(cmp.object,
                reduction = "umap",
                group.by = "orig.ident", 
                split.by = "clust.ID",
                cols = c("gray", "orange", "blue", "red", "green"),)

Gene expression of old clustrs on new map

Let’s see if we can get some gene expression profiles on these…

gene.list <- c("Gata1", "Gata2", "Pf4", "Dntt", "Mpo", "Meis1", "Irf8", "Elane", "Fli1", "Zfpm1")
VlnPlot(cmp.object, features = gene.list, group.by = "clust.ID", pt.size = 0.01, cols = c("gray", "orange", "blue", "red", "green"))

Used the exce doc to do some fancy conditional formatting. Old cluster 17 is pretty dispersed until you it resolution 2.5. Otherise, cells in old cluster 17 do not constitute more than 40% of any cells in the new clusters.
As far as I can see, the two approaches are to do DGEof new CMP w/ resolution = 2.5, AND/OR do DGe using older cluster IDs. Sure seems to make sense to do both…

DGE w/ resolution = 2.5

Strt with comparing all clusters against all other clusters Write out cluster info

calculate FindAllMarkers() for different idents and save to new file

ident.list <- c("RNA_snn_res.0.5", "RNA_snn_res.1", "RNA_snn_res.1.5", "RNA_snn_res.2", "RNA_snn_res.2.5", "clust.ID")
for(tested.ident in ident.list){
    Idents(cmp.object) <- tested.ident
    all.markers <- FindAllMarkers(cmp.object)
    xlsx::write.xlsx(x = all.markers[,c("avg_log2FC", "p_val_adj", "cluster", "gene")], 
                                     file = paste0(projectName, "_FindALLMarkers_res2.5.xlsx"), 
                                     sheetName = tested.ident, 
                                     col.names = TRUE, 
                                     row.names = FALSE, 
                                     append = TRUE)
}

Create FindAllMarkers() lists for GSEA

Idents(cmp.object) <- "RNA_snn_res.2.5"
res.2.5.allmarkers <- FindAllMarkers(cmp.object)

Map HGNC symbols

Mouse2HumanTable <- Mouse2Human(res.2.5.allmarkers$gene)

HGNC <- with(Mouse2HumanTable, Mouse2HumanTable$HGNC[match(res.2.5.allmarkers$gene, Mouse2HumanTable$MGI)])
head(res.2.5.allmarkers)
res.2.5.allmarkers$HGNC <- HGNC
tail(res.2.5.allmarkers)
sig.res.2.5 <- res.2.5.allmarkers[res.2.5.allmarkers$p_val_adj <= 0.05, ]
sig.res.2.5 <- sig.res.2.5[c("avg_log2FC", "HGNC", "cluster")]
sig.res.2.5 <- sig.res.2.5[!(sig.res.2.5$HGNC == "" | is.na(sig.res.2.5$HGNC)),] # GSEA will fail if there are any blanks or NAs in the table
sig.res.2.5 <- sig.res.2.5[]
for(cluster in unique(sig.res.2.5$cluster)){
    print(paste("writing cluster", cluster))
    new.table <- sig.res.2.5[sig.res.2.5$cluster == cluster, c("HGNC", "avg_log2FC")]
    new.table <- new.table[order(-new.table$avg_log2FC), ]
    write.table(new.table, file = paste0("RankList_res2.5_findAll_hgnc/res.2.5cluster", cluster, ".rnk"), quote = FALSE, row.names = FALSE, col.names = TRUE, sep = "\t", )
    
}

calculate FindMarkers() that distinguish each cluster (might overlab between clusters)

ident.list <- c("RNA_snn_res.0.5", "RNA_snn_res.1", "RNA_snn_res.1.5", "RNA_snn_res.2", "RNA_snn_res.2.5", "clust.ID")
for(tested.ident in ident.list){
    for (cluster in sort(as.numeric(levels(cmp.object@meta.data[[tested.ident]])))){
    cluster.markers <- FindMarkers(cmp.object, ident.1 = cluster)
    xlsx::write.xlsx(x = cluster.markers[,c("avg_log2FC", "p_val_adj")], 
                                     file = paste0(projectName, "_FindMarkers_", gsub("RNA_snn_", "", tested.ident), ".xlsx"), 
                                     sheetName = paste0("clst", cluster), 
                                     col.names = TRUE, 
                                     row.names = TRUE, 
                                     append = TRUE)
}
}
for (cluster in sort(as.numeric(levels(cmp.object@meta.data$RNA_snn_res.2.5)))){
    cluster.markers <- FindMarkers(cmp.object, ident.1 = cluster)
    xlsx::write.xlsx(x = cluster.markers[,c("avg_log2FC", "p_val_adj")], 
                                     file = paste0(projectName, "_FindMarkers_res2.5.xlsx"), 
                                     sheetName = paste0("clst", cluster), 
                                     col.names = TRUE, 
                                     row.names = TRUE, 
                                     append = TRUE)
}

Combine clusters that might represent old cluster ids

DGE w/ metadata against clust.ID against “0”

reset ident as “clust.ID” and rerun FindAllMarkers()

    Idents(cmp.object) <- "clust.ID"
    all.markers <- FindAllMarkers(cmp.object)
    xlsx::write.xlsx(x = all.markers[,c("avg_log2FC", "p_val_adj", "cluster", "gene")], 
                                     file = paste0(projectName, "_FindALLMarkers_clustID.xlsx"), 
                                     sheetName = "clustID", 
                                     col.names = TRUE, 
                                     row.names = FALSE, 
                                     append = TRUE)
# Idents(cmp.object) <- "clust.ID"
for (cluster in unique(cmp.object@meta.data$clust.ID)){
    print(cluster)
    cluster.markers <- FindMarkers(cmp.object, ident.1 = cluster)
    xlsx::write.xlsx(x = cluster.markers[,c("avg_log2FC", "p_val_adj")], 
                                     file = paste0(projectName, "_FindMarkers_clustID.xlsx"), 
                                     sheetName = paste0("oldclust", cluster), 
                                     col.names = TRUE, 
                                     row.names = TRUE, 
                                     append = TRUE)
}

Distinguishing features of clusters

Previously defined biomark genes based on PC contributions. Original list was based on all msAggr, but let’s see how CMP subset does?

VizDimLoadings(cmp.object, dims = 1:10, nfeatures = 30, reduction = "pca", ncol = 2)
pca.df <- cmp.object[["pca"]]
pca.df <- as.data.frame(as.matrix(slot(object = pca.df, name = "feature.loadings")))
print(cmp.object[["pca"]], dims = 2, nfeatures = 5)
rownames(pca.df[pca.df$PC_2 %in% sort(pca.df$PC_2, decreasing = TRUE)[1:5], ])
rownames(pca.df[pca.df$PC_2 %in% sort(pca.df$PC_2)[1:5], ])

now we can get a list of principal components!
first pull the list of oldAnalysis CMP top PC genes

cmp.biomark <- read.table(file = "/Users/heustonef/Desktop/CMPSubpops/BioMark/ProbePanels/CMP_PCTopGenes.txt", sep = "\t", header = TRUE)
biomark.cmptargets <- c()
for(df.col in 1:ncol(cmp.biomark)){
    biomark.cmptargets <- c(biomark.cmptargets, biomark[,df.col])
}
print(colnames(biomark))
print(paste("total gene count:", length(biomark.cmptargets)))

Now get the list of current pc gene trgets (oldAnalysis used ndim = 1:6, so we’ll start with that range)

pc.list <- c("PC_1", "PC_2", "PC_3", "PC_4", "PC_5", "PC_6")
pc.genes <- lapply(pc.list, function(x) rownames(pca.df[pca.df[[x]] %in% sort(pca.df[[x]], decreasing = TRUE)[1:30],])) #targeting roughly 180 genes like in biomark.cmptargets
pc.genes <- unique(unlist(pc.genes))
print(paste("total gene count:", length(pc.genes)))

Now compare the lists, I guess:

# setdiff(x,y) gives you things in x not in y. setdiff(y,x) gives you things in y not in x
setdiff(biomark.cmptargets, pc.genes)
# print(paste("\n length:", length(setdiff(biomark.cmptargets, pc.genes))))
writeLines(c("", "length:", length(setdiff(biomark.cmptargets, pc.genes))))

Umm, yeah that went kinda how I expected. Let’s do this again, but for the actual biomark gene lists.

biomark <- read.table(file = "/Users/heustonef/Desktop/CMPSubpops/BioMark/ProbePanels/BiomarkProbeList.txt", sep = "\t")
biomark <- biomark[,1]
setdiff(biomark, pc.genes)
writeLines(c("", "length:", length(setdiff(biomark, pc.genes))))

What if we increase the number of pcs but decrease the depth of each? This might cover more of biomark, which was originally developed using msAggr instead of only the CMP subset

pc.list <- c("PC_1", "PC_2", "PC_3", "PC_4", "PC_5", "PC_6", "PC_7", "PC_8", "PC_9", "PC_10")
pc.genes <- lapply(pc.list, function(x) rownames(pca.df[pca.df[[x]] %in% sort(pca.df[[x]], decreasing = TRUE)[1:20],]))
pc.genes <- unique(unlist(pc.genes))
print(paste("total gene count:", length(pc.genes)))
setdiff(biomark, pc.genes)
writeLines(c("", "length:", length(setdiff(biomark, pc.genes))))

For comparison, let’s just see how many of biomark.cmptargets were actually included in biomark

setdiff(biomark.cmptargets, biomark)
writeLines(c("", "length:", length(setdiff(biomark.cmptargets, pc.genes))))
length(biomark) - length(setdiff(biomark, biomark.cmptargets))
length(biomark) - length(setdiff(biomark, pc.genes))

So when you look at it like that, it’s not actually that far off.

What are the similarities?:

setdiff(setdiff(biomark, biomark.cmptargets), setdiff(biomark, pc.genes))

These are genes from the 97probes not in the old CMP set that are also not in the new CMP set. Other than Itga2b (which is a failed probe anyway), nothing screams. Also we’d have thrown Flt3 and Cd34 for in anyway because they’re requisite cell surface markers (also Flt3 surface marker is expensive but otherwise not noteworthy and not used in the current sorting strategy)

What about cell surface marker expression? * Cd34 * Cd16/32 * Cd9 * Cd41 * Cd48 * Sca1 (just throw that in for sh*&s and giggles)

surface.markers <- c("Cd34", "Fcgr3", "Fcgr2b", "Cd9", "Itga2b", "Cd48", "Ly6a")
FeaturePlot(cmp.object, features = surface.markers, pt.size = 1, split.by = "clust.ID", ncol = 1)

Save as png

png(filename = "FeaturePlot_CMP_surfaceMarkers_clustIDfacet.png", height = 1600, width = 1600)
FeaturePlot(cmp.object, features = surface.markers, pt.size = 1, split.by = "clust.ID", ncol = 1)
dev.off()

Cell cycle analysis

Just so we know what we’re working with

s.genes <- cc.genes.updated.2019$s.genes
g2m.genes <- cc.genes.updated.2019$g2m.genes

Compare @ hierarchcial clusteirng

Do clustering using biomark RNAs as input

# Read in BiomarkRNAs
biomark.rnas <- read.table('/Users/heustonef/Desktop/10XGenomicsData/BiomarkRNAs.txt')
biomark.rnas <- biomark.rnas$V1

use biomark RNAs to define dimensional reduction

cmp.object <- readRDS("CMP_raw.RDS")
cmp.object <- RunPCA(cmp.object, features = biomark.rnas, ndims.print = 1:5, , nfeatures.print = 5)
ElbowPlot(cmp.object, ndims = 50)

Now run the clustering

tot.var <- percent.variance(cmp.object@reductions$pca@stdev, plot.var = FALSE, return.val = TRUE)
ndims <- length(which(cumsum(tot.var) <= 90))

cmp.object <- FindNeighbors(cmp.object, dims = 1:ndims)
cmp.object <- FindClusters(cmp.object, resolution = 0.5)
cmp.object <- RunUMAP(cmp.object, dims = 1: ndims)

find the clusters

for(x in c(0.5, 1, 1.5, 2, 2.5)){
    cmp.object <- FindClusters(cmp.object, resolution = x)
}

Plot the umaps and cell cluster ids

for (meta.col in colnames(cmp.object@meta.data)){
    if(grepl(pattern = ("RNA_snn_res"), x = meta.col)==TRUE){
        myplot <- DimPlot(cmp.object, 
                                            group.by = meta.col,
                                            reduction = "umap", 
                                            cols = color.palette
                                            ) + 
            ggtitle(paste0(projectName, " dim", ndims, "res", gsub("RNA_snn_res", "", meta.col) ))
        plot(myplot)
    }
}

Calculate anticipated number of cells you’ll find in each biomark cluster

Get # cells in each cluster

tot.cellcount <- nrow(cmp.object@meta.data)
res05.list <- sort(unique(cmp.object@meta.data$RNA_snn_res.0.5), decreasing = FALSE)
sapply(res05.list, 
             function(x){
                print(
                    paste(
                        "cluster", x, "=", 
                        nrow(cmp.object@meta.data[cmp.object@meta.data$RNA_snn_res.0.5 == x,]), 
                        "cells or", 
                        round(nrow(cmp.object@meta.data[cmp.object@meta.data$RNA_snn_res.0.5 == x,])/tot.cellcount*100, digits = 2), 
                        "% of total"
                    )
                )
             }
            )

So we did the dimensional reduction based on the biomark RNAs, then did our UMAP nearest neighbor clustering.

In the biomark hierarchcial clustering analysis I assayed 167 cells. The smallest cluster I detected had 3 cells, or 1.8% of total, and this is an uncomfortably small number of cells. Based on the UMAP calculations I would therefore expect to find 11 or 12 of the predicted 15 clusters. I found 12, and I don’t really like that last one, so 11 or 12. Since I did the hierarchcial clustering yesterday and did this math today, we can say it was independent of these results and therefore totally legit. Yay!!

LS0tCnRpdGxlOiAiQ01QU3Vic2V0IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpDcmVhdGluZyBuZXcgcGlwZWxpbmUgdXNpbmcgc2V1cmF0IHY0LjAuMiBhdmFpbGFibGUgMjAyMS4wNi4wOAoKTG9hZCBsaWJyYXJpZXMgcmVxdWlyZWQgZm9yIFNldXJhdHY0CgpgYGB7ciBzZXR1cH0Ka25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAifi9EZXNrdG9wLzEwWEdlbm9taWNzRGF0YS9tc0FnZ3Jfc2NSTkFTZXEvIikKbGlicmFyeShkcGx5cikKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGdncGxvdDIpCiMgbGlicmFyeShjbHVzdHJlZSkKYGBgCnN0b3JlIHNlc3Npb24gaW5mbwpgYGB7ciB9CiMgc2luaygiQ01QLXYxLjIwMjEwNjE2LnR4dCIpCnNlc3Npb25JbmZvKCkKIyBzaW5rKCkKYGBgCgojIFNldCBnbG9iYWwgdmFyaWFibGVzCmBgYHtyfQpwcm9qZWN0TmFtZSA8LSAiQ01QIgpqYWNrc3RyYXcuZGltIDwtIDQwCmBgYAoKCgpgYGB7cn0Kc291cmNlKCJtc0FnZ3JfQW5hbHlzaXNDb2RlL3JlYWRfMTBYR2Vub21pY3NfZGF0YS5SIikKc291cmNlKCJtc0FnZ3JfQW5hbHlzaXNDb2RlL1BlcmNlbnRWYXJpYW5jZS5SIikKc291cmNlKCJtc0FnZ3JfQW5hbHlzaXNDb2RlL01vdXNlMkh1bWFuX2lkY29udmVyc2lvbi5SIikKYGBgCgoKYGBge3J9CnNldHdkKCIuLi9jZWxsUmFuZ2VyLyIpICMgdGVtcG9yYXJpbHkgY2hhbmdpbmcgd2Qgb25seSB3b3JrcyBpZiB5b3UgcnVuIHRoZSBlbnRpcmUgY2h1bmsgYXQgb25jZQpkYXRhX2ZpbGUubGlzdCA8LSByZWFkXzEwWEdlbm9taWNzX2RhdGEoc2FtcGxlLmxpc3QgPSAiQ01QbTIiKQpvYmplY3QuZGF0YSA8LVJlYWQxMFgoZGF0YV9maWxlLmxpc3QpCmBgYAoKCgpgYGB7cn0KY21wLm9iamVjdDwtIENyZWF0ZVNldXJhdE9iamVjdChjb3VudHMgPSBvYmplY3QuZGF0YSwgbWluLmNlbGxzID0gMywgbWluLmdlbmVzID0gMjAwLCBwcm9qZWN0ID0gcHJvamVjdE5hbWUpCmBgYAoKQ2xlYW4gdXAgdG8gZnJlZSBtZW1vcnkKCmBgYHtyfQpyZW1vdmUob2JqZWN0LmRhdGEpCmBgYAoKCkFkZCBtaXRvY2hvbmRyaWFsIG1ldGFkYXRhIGFuZCBwbG90IHNvbWUgYmFzaWMgZmVhdHVyZXMKYGBge3J9CmNtcC5vYmplY3RbWyJwZXJjZW50Lm10Il1dIDwtIFBlcmNlbnRhZ2VGZWF0dXJlU2V0KGNtcC5vYmplY3QsIHBhdHRlcm4gPSAiXm10LSIpClZsblBsb3QoY21wLm9iamVjdCwgZmVhdHVyZXMgPSBjKCJuRmVhdHVyZV9STkEiLCAibkNvdW50X1JOQSIsICJwZXJjZW50Lm10IiksIG5jb2wgPSAzLCBwdC5zaXplID0gMCwgZmlsbC5ieSA9ICdvcmlnLmlkZW50JywgKQpgYGAKCgpgYGB7cn0KcGxvdDEgPC0gRmVhdHVyZVNjYXR0ZXIoY21wLm9iamVjdCwgZmVhdHVyZTEgPSAibkNvdW50X1JOQSIsIGZlYXR1cmUyID0gInBlcmNlbnQubXQiLCBncm91cC5ieSA9ICJvcmlnLmlkZW50IiwgcHQuc2l6ZSA9IDAuMDEpCnBsb3QyIDwtIEZlYXR1cmVTY2F0dGVyKGNtcC5vYmplY3QsIGZlYXR1cmUxID0gIm5Db3VudF9STkEiLCBmZWF0dXJlMiA9ICJuRmVhdHVyZV9STkEiLCBncm91cC5ieSA9ICJvcmlnLmlkZW50IiwgcHQuc2l6ZSA9IDAuMDEpCnBsb3QxICsgcGxvdDIKYGBgCgpXZSBkb24ndCBoYXZlIHRvIHdvcnJ5IGFib3V0IGNvbXBhcmluZyBsaWJyYXJ5IGRlcHRocywgc28gd2UnbGwganVzdCBkbyBub3JtYWxpemF0aW9uL1NjYWxlIGRhdGEKCgoKcmVtb3ZlIGxvdyBxdWFsaXR5IGNlbGxzCnJlcXVpcmU6IG5GZWF0dXJlX1JOQSBiZXR3ZWVuIDIwMCBhbmQgNDAwMCAoaW5jbHVzaXZlKQpyZXF1aXJlOiBwZXJjZW50Lm10IDw9NQoKYGBge3J9CnByaW50KHBhc3RlKCJvcmlnaW5hbCBvYmplY3Q6IiwgbnJvdyhjbXAub2JqZWN0QG1ldGEuZGF0YSksICJjZWxscyIsIHNlcCA9ICIgIikpCmNtcC5vYmplY3QgPC0gc3Vic2V0KGNtcC5vYmplY3QsIAoJCQkJCQkJCQkJCQlzdWJzZXQgPSBuRmVhdHVyZV9STkEgPj0yMDAgJiAKCQkJCQkJCQkJCQkJCW5GZWF0dXJlX1JOQSA8PSA0MDAwICYgCgkJCQkJCQkJCQkJCQlwZXJjZW50Lm10IDw9IDUKCQkJCQkJCQkJCQkJKQpwcmludChwYXN0ZSgibmV3IG9iamVjdDoiLCBucm93KGNtcC5vYmplY3RAbWV0YS5kYXRhKSwgImNlbGxzIiwgc2VwID0gIiAiKSkKYGBgCgoKCmBgYHtyfQpjbXAub2JqZWN0IDwtIE5vcm1hbGl6ZURhdGEoY21wLm9iamVjdCwgbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiTG9nTm9ybWFsaXplIiwgc2NhbGUuZmFjdG9yID0gMTAwMDApCmBgYAoKCkZpbmQgdmFyaWFibGUgZmVhdHVyZXMKYGBge3IgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDJ9CmNtcC5vYmplY3QgPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMoY21wLm9iamVjdCwgc2VsZWN0aW9uLm1ldGhvZCA9ICJ2c3QiLCBuZmVhdHVyZXMgPSAyMDAwKQp0b3AxMCA8LSBoZWFkKFZhcmlhYmxlRmVhdHVyZXMoY21wLm9iamVjdCksIDEwKQpwbG90MSA8LSBWYXJpYWJsZUZlYXR1cmVQbG90KGNtcC5vYmplY3QpCnBsb3QyIDwtIExhYmVsUG9pbnRzKHBsb3QgPSBwbG90MSwgcG9pbnRzID0gdG9wMTAsIHJlcGVsID0gVFJVRSkKcGxvdDEgKyBwbG90MgoKYGBgCgpTY2FsZSBkYXRhIChsaW5lYXIgdHJhbnNmb3JtYXRpb24pCgpgYGB7ciBlY2hvID0gRkFMU0V9CmFsbC5nZW5lcyA8LSByb3duYW1lcyhjbXAub2JqZWN0KQpjbXAub2JqZWN0IDwtIFNjYWxlRGF0YShjbXAub2JqZWN0LCBmZWF0dXJlcyA9IGFsbC5nZW5lcywgdmFycy50by5yZWdyZXNzID0gYygibkZlYXR1cmVfUk5BIiwgIm5Db3VudF9STkEiKSkKYGBgCgojIyBTYXZlIHJhdyBvYmplY3QKYGBge3J9CnNhdmVSRFMoY21wLm9iamVjdCwgZmlsZSA9IHBhc3RlMChwcm9qZWN0TmFtZSwgIl9yYXcuUkRTIikpCmBgYAoKCgpgYGB7cn0KY21wLm9iamVjdCA8LSBSdW5QQ0EoY21wLm9iamVjdCwgZmVhdHVyZXMgPSBWYXJpYWJsZUZlYXR1cmVzKGNtcC5vYmplY3QpLCBuZGltcy5wcmludCA9IDE6NSwgbmZlYXR1cmVzLnByaW50ID0gNSkKYGBgCgpgYGB7cn0KRGltUGxvdChjbXAub2JqZWN0LCByZWR1Y3Rpb24gPSAicGNhIiwgZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIpClZpekRpbUxvYWRpbmdzKGNtcC5vYmplY3QsIGRpbXMgPSAxOjYsIG5mZWF0dXJlcyA9IDEwLCByZWR1Y3Rpb24gPSAicGNhIiwgbmNvbCA9IDMpCgpgYGAKCkNhbGN1bGF0ZSBkaW1lbnNpb25hbGl0eQpgYGB7ciwgZmlndXJlcy1zaWRlLCBmaWcuc2hvdz0naG9sZCcsIG91dC53aWR0aD0iNTAlIn0KRWxib3dQbG90KGNtcC5vYmplY3QsIG5kaW1zID0gNTApCnBlcmNlbnQudmFyaWFuY2UoY21wLm9iamVjdEByZWR1Y3Rpb25zJHBjYUBzdGRldikKYGBgCk51bWJlciBvZiBQQ3MgZGVzY3JpYmluZyBYJSBvZiB2YXJpYW5jZQoKYGBge3J9CnRvdC52YXIgPC0gcGVyY2VudC52YXJpYW5jZShjbXAub2JqZWN0QHJlZHVjdGlvbnMkcGNhQHN0ZGV2LCBwbG90LnZhciA9IEZBTFNFLCByZXR1cm4udmFsID0gVFJVRSkKcGFzdGUwKCJOdW0gcGNzIGZvciA4MCUgdmFyaWFuY2U6IiwgbGVuZ3RoKHdoaWNoKGN1bXN1bSh0b3QudmFyKSA8PSA4MCkpKQpwYXN0ZTAoIk51bSBwY3MgZm9yIDg1JSB2YXJpYW5jZToiLCBsZW5ndGgod2hpY2goY3Vtc3VtKHRvdC52YXIpIDw9IDg1KSkpCnBhc3RlMCgiTnVtIHBjcyBmb3IgOTAlIHZhcmlhbmNlOiIsIGxlbmd0aCh3aGljaChjdW1zdW0odG90LnZhcikgPD0gOTApKSkKcGFzdGUwKCJOdW0gcGNzIGZvciA5NSUgdmFyaWFuY2U6IiwgbGVuZ3RoKHdoaWNoKGN1bXN1bSh0b3QudmFyKSA8PSA5NSkpKQoKYGBgCgojIEFkZCBjbHVzdGVyIElEcyBmcm9tIFNldXJhdCB2MQoKRXhwb3J0ZWQgY2VsbCBJRHMgZm9yIGNsdXN0ZXJzIDMsIDE3LCAxMCwgMTEgZnJvbSBTZXVyYXQgdjEuIFdpbGwgYWRkIHRoZXNlIElEcyBhcyBhIG1ldGFkYXRhIGNvbHVtbi4gIApDcmVhdGUgY29sdW1uICJjbHVzdC5JRCIgYW5kIHBvcHVsYXRlIHdpdGggMCdzLiBUaGVuIGltcG9ydCBJRHMgZm9yIGNsdXN0ZXJzCgoKCmBgYHtyfQpjbHVzdDMuY2VsbHMgPC0gcmVhZC50YWJsZShmaWxlID0gIlNldXJhdHYxX2NsdXN0ZXJDZWxsSURzL2NsdXN0ZXIzY2VsbElEcy50eHQiLCBjb2wubmFtZXMgPSAiY2x1c3QwMyIpCmNsdXN0My5jZWxscyA8LSBzYXBwbHkoY2x1c3QzLmNlbGxzLCBmdW5jdGlvbih4KSBwYXN0ZTAoZ3N1YigiQ01QIiwgIkNNUG0yIiwgeCksICItMSIpKQpjbHVzdDE3LmNlbGxzIDwtIHJlYWQudGFibGUoZmlsZSA9ICJTZXVyYXR2MV9jbHVzdGVyQ2VsbElEcy9jbHVzdGVyMTdjZWxsSURzLnR4dCIsIGNvbC5uYW1lcyA9ICJjbHVzdDE3IikKY2x1c3QxNy5jZWxscyA8LSBzYXBwbHkoY2x1c3QxNy5jZWxscywgZnVuY3Rpb24oeCkgcGFzdGUwKGdzdWIoIkNNUCIsICJDTVBtMiIsIHgpLCAiLTEiKSkKY2x1c3QxMC5jZWxscyA8LSByZWFkLnRhYmxlKGZpbGUgPSAiU2V1cmF0djFfY2x1c3RlckNlbGxJRHMvY2x1c3RlcjEwY2VsbElEcy50eHQiLCBjb2wubmFtZXMgPSAiY2x1c3QxMCIpCmNsdXN0MTAuY2VsbHMgPC0gc2FwcGx5KGNsdXN0MTAuY2VsbHMsIGZ1bmN0aW9uKHgpIHBhc3RlMChnc3ViKCJDTVAiLCAiQ01QbTIiLCB4KSwgIi0xIikpCmNsdXN0MTEuY2VsbHMgPC0gcmVhZC50YWJsZShmaWxlID0gIlNldXJhdHYxX2NsdXN0ZXJDZWxsSURzL2NsdXN0ZXIxMWNlbGxJRHMudHh0IiwgY29sLm5hbWVzID0gImNsdXN0MTEiKQpjbHVzdDExLmNlbGxzIDwtIHNhcHBseShjbHVzdDExLmNlbGxzLCBmdW5jdGlvbih4KSBwYXN0ZTAoZ3N1YigiQ01QIiwgIkNNUG0yIiwgeCksICItMSIpKQpgYGAKCkFkZCBuZXcgbWV0YWRhdGEgY29sdW1uCmBgYHtyfQpjbXAub2JqZWN0QG1ldGEuZGF0YVsnY2x1c3QuSUQnXSA8LSAwCmhlYWQoY21wLm9iamVjdEBtZXRhLmRhdGEpCmBgYAoKbm93IG1hcCBuZXcgaWRzCmBgYHtyfQpjbXAub2JqZWN0QG1ldGEuZGF0YSRjbHVzdC5JRFtyb3duYW1lcyhjbXAub2JqZWN0QG1ldGEuZGF0YSkgJWluJSBjbHVzdDMuY2VsbHNdIDwtIDMKY21wLm9iamVjdEBtZXRhLmRhdGEkY2x1c3QuSURbcm93bmFtZXMoY21wLm9iamVjdEBtZXRhLmRhdGEpICVpbiUgY2x1c3QxNy5jZWxsc10gPC0gMTcKY21wLm9iamVjdEBtZXRhLmRhdGEkY2x1c3QuSURbcm93bmFtZXMoY21wLm9iamVjdEBtZXRhLmRhdGEpICVpbiUgY2x1c3QxMC5jZWxsc10gPC0gMTAKY21wLm9iamVjdEBtZXRhLmRhdGEkY2x1c3QuSURbcm93bmFtZXMoY21wLm9iamVjdEBtZXRhLmRhdGEpICVpbiUgY2x1c3QxMS5jZWxsc10gPC0gMTEKYGBgCgpkbyBudW1iZXJzIG1ha2Ugc2Vuc2U/CmBgYHtyfQpucm93KGNtcC5vYmplY3RAbWV0YS5kYXRhW2NtcC5vYmplY3RAbWV0YS5kYXRhJGNsdXN0LklEID09IDEwLF0pCm5yb3coY21wLm9iamVjdEBtZXRhLmRhdGFbY21wLm9iamVjdEBtZXRhLmRhdGEkY2x1c3QuSUQgPT0gMTEsXSkKbnJvdyhjbXAub2JqZWN0QG1ldGEuZGF0YVtjbXAub2JqZWN0QG1ldGEuZGF0YSRjbHVzdC5JRCA9PSAxNyxdKQpucm93KGNtcC5vYmplY3RAbWV0YS5kYXRhW2NtcC5vYmplY3RAbWV0YS5kYXRhJGNsdXN0LklEID09IDMsXSkKYGBgCgojIyMgQ29sb3IgcGFsZXR0ZQpgYGB7cn0KY29sb3IucGFsZXR0ZSA8LSBjKAoJImNvcmFsIiwKCSJjaGFydHJldXNlNCIsCgkiZ29sZGVucm9kMSIsCgkiY2FkZXRibHVlMSIsCgkiYnVybHl3b29kIiwKCSJicm93biIsCgkiYnJvd24xIiwKCSJibHVlIiwKCSJibHVlNCIsCgkiYXp1cmUzIiwKCSJhcXVhbWFyaW5lIiwKCSJhbnRpcXVld2hpdGUiLAoJImNhZGV0Ymx1ZSIsCgkiZ29sZDMiLAoJImJsYWNrIiwKCSJkYXJrZ3JlZW4iLAoJImRlZXBwaW5rIiwKCSJkYXJrdmlvbGV0IiwKCSJkYXJrdHVycXVvaXNlIiwKCSJkYXJrc2xhdGVncmF5IiwKCSJkYXJrc2FsbW9uIiwKCSJkYXJrb3JjaGlkMSIsCgkiZGFya29saXZlZ3JlZW4yIiwKCSJmb3Jlc3RncmVlbiIsCgkiZG9kZ2VyYmx1ZSIsCgkiZ3JlZW4iLAoJImxpZ2h0cGluayIsCgkibGlnaHRjb3JhbCIsCgkia2hha2kxIiwKCSJtYXJvb24iLAoJInBlcnUiLAoJImxpZ2h0c2VhZ3JlZW4iLAoJImxpZ2h0c2FsbW9uIiwKCSJwbHVtIiwKCSJtb2NjYXNpbiIsCgkidGFuIiwKCSJ0YW4xIiwgCgkicmVkIiwgCgkicHVycGxlIiwKCSJraGFraTQiLAoJImJsYWNrIiwgCgkicGx1bTQiCikKYGBgCgojIFRvdGFsIHZhciA5MCUKIyMgTmVpZ2hib3Job29kIGFuZCB1bWFwCnNldCB0b3RhbC52YXIgPC0gOTAlCmBgYHtyfQp0b3QudmFyIDwtIHBlcmNlbnQudmFyaWFuY2UoY21wLm9iamVjdEByZWR1Y3Rpb25zJHBjYUBzdGRldiwgcGxvdC52YXIgPSBGQUxTRSwgcmV0dXJuLnZhbCA9IFRSVUUpCm5kaW1zIDwtIGxlbmd0aCh3aGljaChjdW1zdW0odG90LnZhcikgPD0gOTApKQoKY21wLm9iamVjdCA8LSBGaW5kTmVpZ2hib3JzKGNtcC5vYmplY3QsIGRpbXMgPSAxOm5kaW1zKQpjbXAub2JqZWN0IDwtIEZpbmRDbHVzdGVycyhjbXAub2JqZWN0LCByZXNvbHV0aW9uID0gMC41KQpjbXAub2JqZWN0IDwtIFJ1blVNQVAoY21wLm9iamVjdCwgZGltcyA9IDE6IG5kaW1zKQoKc2F2ZVJEUyhjbXAub2JqZWN0LCBmaWxlID0gcGFzdGUwKHByb2plY3ROYW1lLCAiX2RpbSIsIG5kaW1zLCAiLlJEUyIpKQpgYGAKUGxvdCBVTUFQCgpgYGB7cn0KZm9yKHggaW4gYygwLjUsIDEsIDEuNSwgMiwgMi41KSl7CgljbXAub2JqZWN0IDwtIEZpbmRDbHVzdGVycyhjbXAub2JqZWN0LCByZXNvbHV0aW9uID0geCkKfQpgYGAKCmBgYHtyfQpmb3IgKG1ldGEuY29sIGluIGNvbG5hbWVzKGNtcC5vYmplY3RAbWV0YS5kYXRhKSl7CglpZihncmVwbChwYXR0ZXJuID0gKCJSTkFfc25uX3JlcyIpLCB4ID0gbWV0YS5jb2wpPT1UUlVFKXsKCQlteXBsb3QgPC0gRGltUGxvdChjbXAub2JqZWN0LCAKCQkJCQkJCQkJCQlncm91cC5ieSA9IG1ldGEuY29sLAoJCQkJCQkJCQkJCXJlZHVjdGlvbiA9ICJ1bWFwIiwgCgkJCQkJCQkJCQkJY29scyA9IGNvbG9yLnBhbGV0dGUKCQkJCQkJCQkJCQkpICsgCgkJCWdndGl0bGUocGFzdGUwKHByb2plY3ROYW1lLCAiIGRpbSIsIG5kaW1zLCAicmVzIiwgZ3N1YigiUk5BX3Nubl9yZXMiLCAiIiwgbWV0YS5jb2wpICkpCgkJcGxvdChteXBsb3QpCgl9Cn0KYGBgCgoKZm9yIGVhY2ggcmVzb2x1dGlvbiwgbnVtYmVyL3BlcmNlbnRhZ2Ugb2YgY2VsbHMgaW4gZWFjaCBjbHVzdGVyPwoKYGBge3J9CnRvdC5jZWxscyA8LSBucm93KGNtcC5vYmplY3RAbWV0YS5kYXRhKQpmb3IgKG1ldGEuY29sIGluIGNvbG5hbWVzKGNtcC5vYmplY3RAbWV0YS5kYXRhKSl7CglpZihncmVwbChwYXR0ZXJuID0gKCJSTkFfc25uX3JlcyIpLCB4ID0gbWV0YS5jb2wpPT1UUlVFKXsKCQluZXcuY2x1c3RlcnMgPC0gc29ydChhcy5udW1lcmljKGxldmVscyhjbXAub2JqZWN0QG1ldGEuZGF0YVtbbWV0YS5jb2xdXSkpKQoJCXN0YXRzLmRmIDwtIGRhdGEuZnJhbWUobWF0cml4KG5jb2wgPSAyLCBucm93ID0gbGVuZ3RoKG5ldy5jbHVzdGVycykpKQoJCWNvbG5hbWVzKHN0YXRzLmRmKSA8LSBjKCJudW1fY2VsbHMiLCAicGN0X3BvcCIpCgkJcm93bmFtZXMoc3RhdHMuZGYpIDwtIG5ldy5jbHVzdGVycwoJCW1ldGEuZGYgPC0gY21wLm9iamVjdEBtZXRhLmRhdGEKCQlmb3Iocm93LmlkIGluIHJvd25hbWVzKHN0YXRzLmRmKSl7CgkJCQludW0ueCA8LSBucm93KG1ldGEuZGZbbWV0YS5kZlttZXRhLmNvbF0gPT0gcm93LmlkLF0pCgkJCQlwY3QueCA8LSBhcy5pbnRlZ2VyKG51bS54IC8gdG90LmNlbGxzICoxMDApCgkJCQkjIHByaW50KHBjdC54KQoJCQkJc3RhdHMuZGZbcm93LmlkLCAibnVtX2NlbGxzIl0gPC0gbnVtLngKCQkJCXN0YXRzLmRmW3Jvdy5pZCwgInBjdF9wb3AiXSA8LSBwY3QueAoJCX0KCQlwcmludChzdGF0cy5kZikKCX0KfQpgYGAKCgoKRm9yIGVhY2ggcmVzb2x1dGlvbiwgd2hhdCBwZXJjZW50YWdlIG9mIGNlbGxzIGluIGVhY2ggY2x1c3RlciBhcmUgZW5yaWNoZWQgZm9yIG9uZSBvZiBvdXIgY2x1c3QuSURzPwoKClRlc3Q6IHdoYXQgcGVyY2VudGFnZSBvZiBlYWNoIG5ldyBjbHVzdGVySUQgbWF0Y2hlcyBvbmUgb2YgdGhlIG9sZGVyIGNsdXN0ZXJzPwpgYGB7cn0KZm9yIChtZXRhLmNvbCBpbiBjb2xuYW1lcyhjbXAub2JqZWN0QG1ldGEuZGF0YSkpewoJaWYoZ3JlcGwocGF0dGVybiA9ICgiUk5BX3Nubl9yZXMiKSwgeCA9IG1ldGEuY29sKT09VFJVRSl7CgkJbmV3LmNsdXN0ZXJzIDwtIHNvcnQoYXMubnVtZXJpYyhsZXZlbHMoY21wLm9iamVjdEBtZXRhLmRhdGFbW21ldGEuY29sXV0pKSkKCQllbnJpY2guZGYgPC0gZGF0YS5mcmFtZShtYXRyaXgobmNvbCA9IDQsIG5yb3cgPSBsZW5ndGgobmV3LmNsdXN0ZXJzKSkpCgkJY29sbmFtZXMoZW5yaWNoLmRmKSA8LSBjKDMsIDE3LCAxMCwgMTEpCgkJcm93bmFtZXMoZW5yaWNoLmRmKSA8LSBuZXcuY2x1c3RlcnMKCQltZXRhLmRmIDwtIGNtcC5vYmplY3RAbWV0YS5kYXRhCgkJZm9yKHJvdy5pZCBpbiByb3duYW1lcyhlbnJpY2guZGYpKXsKCQkJdG90LmNsdXMgPC0gbnJvdyhtZXRhLmRmW21ldGEuZGZbW21ldGEuY29sXV0gPT0gcm93LmlkLF0pCgkJCWZvcihjb2wuaWQgaW4gY29sbmFtZXMoZW5yaWNoLmRmKSl7CgkJCQludW0ueCA8LSBucm93KG1ldGEuZGZbKG1ldGEuZGZbW21ldGEuY29sXV0gPT0gcm93LmlkKSAmIChtZXRhLmRmJGNsdXN0LklEID09IGNvbC5pZCksXSkKCQkJCXBjdC54IDwtIGFzLmludGVnZXIobnVtLnggLyB0b3QuY2x1cyAqMTAwKQoJCQkJIyBwcmludChwY3QueCkKCQkJCWVucmljaC5kZltyb3cuaWQsIGNvbC5pZF0gPC0gcGN0LngKCQkJfQoJCX0KCQljb2xuYW1lcyhlbnJpY2guZGYpIDwtIHNhcHBseShjb2xuYW1lcyhlbnJpY2guZGYpLCBmdW5jdGlvbih4KSBwYXN0ZTAoIm9sZGNsdXN0ZXIiLCB4KSkKCQlyb3duYW1lcyhlbnJpY2guZGYpIDwtIHNhcHBseShyb3duYW1lcyhlbnJpY2guZGYpLCBmdW5jdGlvbih4KSBwYXN0ZTAoIm5ld2NsdXN0ZXIiLCB4KSkKCQl4bHN4Ojp3cml0ZS54bHN4KGVucmljaC5kZiwgZmlsZSA9IHBhc3RlMCgiUGN0T2ZOZXdDbHVzdGVyc092ZXJsYXBwaW5nT2xkQ2x1c3RlcnNfIiwgcHJvamVjdE5hbWUsICJfZGltIiwgbmRpbXMsICIueGxzeCIpLCBzaGVldE5hbWUgPSBwYXN0ZTAoZ3N1YigiUk5BX3Nubl8iLCAiIiwgbWV0YS5jb2wpKSwgYXBwZW5kID0gVFJVRSkKCQlwcmludChlbnJpY2guZGYpCgl9Cn0KCmBgYApBYnNvbHV0ZWx5IHRlcnJpYmxlIG92ZXJsYXAsIG5vIGVucmljaG1lbnQgb2YgYW55IG9mIHRoZXNlIGFjcm9zcyB0aGUgbmV3IGNsdXN0ZXJpbmcgYWxnb3JpdGhtLiBNYXliZSBzaG91bGQgdHJ5IDk1JSB2YXJpYXRpb24gY292ZXJlZAoKIyMgRmluZCBvbGQgY2VsbHMgb24gVU1BUAoKdGltZSBmb3IgdGhlIHN1cGVyIHNjYXJleSBtb21lbnQgdG8gc2VlIGlmIHRoZSBjZWxscyBmcm9tIHNldXJhdHYxIHN0aWxsIGNsdXN0ZXIgdG9nZXRoZXIgb24gaW4gc2V1cmF0IHY0CgpgYGB7ciBmaWcud2lkdGggPSA0fQpEaW1QbG90KGNtcC5vYmplY3QsCgkJCQlyZWR1Y3Rpb24gPSAidW1hcCIsCgkJCQlncm91cC5ieSA9ICJjbHVzdC5JRCIsIAoJCQkJIyBzcGxpdC5ieSA9ICJvcmlnLmlkZW50IiwKCQkJCWNvbHMgPSBjKCJncmF5IiwgIm9yYW5nZSIsICJibHVlIiwgInJlZCIsICJncmVlbiIpLCkKYGBgCmBgYHtyIGZpZy53aWR0aCA9IDR9CkRpbVBsb3QoY21wLm9iamVjdCwKCQkJCXJlZHVjdGlvbiA9ICJ1bWFwIiwKCQkJCWdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiLCAKCQkJCXNwbGl0LmJ5ID0gImNsdXN0LklEIiwKCQkJCWNvbHMgPSBjKCJncmF5IiwgIm9yYW5nZSIsICJibHVlIiwgInJlZCIsICJncmVlbiIpLCkKYGBgCgoKIyBUb3RhbCB2YXIgOTUlCiMjIE5laWdoYm9yaG9vZCBhbmQgdW1hcApzZXQgdG90YWwudmFyIDwtIDk1JQpgYGB7cn0KdG90LnZhciA8LSBwZXJjZW50LnZhcmlhbmNlKGNtcC5vYmplY3RAcmVkdWN0aW9ucyRwY2FAc3RkZXYsIHBsb3QudmFyID0gRkFMU0UsIHJldHVybi52YWwgPSBUUlVFKQpuZGltcyA8LSBsZW5ndGgod2hpY2goY3Vtc3VtKHRvdC52YXIpIDw9IDk1KSkKCmNtcC5vYmplY3QgPC0gRmluZE5laWdoYm9ycyhjbXAub2JqZWN0LCBkaW1zID0gMTpuZGltcykKY21wLm9iamVjdCA8LSBGaW5kQ2x1c3RlcnMoY21wLm9iamVjdCwgcmVzb2x1dGlvbiA9IDAuNSkKY21wLm9iamVjdCA8LSBSdW5VTUFQKGNtcC5vYmplY3QsIGRpbXMgPSAxOiBuZGltcykKCnNhdmVSRFMoY21wLm9iamVjdCwgZmlsZSA9IHBhc3RlMChwcm9qZWN0TmFtZSwgIl9kaW0iLCBuZGltcywgIi5SRFMiKSkKYGBgClBsb3QgVU1BUAoKYGBge3J9CmZvcih4IGluIGMoMC41LCAxLCAxLjUsIDIsIDIuNSkpewoJY21wLm9iamVjdCA8LSBGaW5kQ2x1c3RlcnMoY21wLm9iamVjdCwgcmVzb2x1dGlvbiA9IHgpCn0KYGBgCgoKCkZvciBlYWNoIHJlc29sdXRpb24sIHdoYXQgcGVyY2VudGFnZSBvZiBjZWxscyBpbiBlYWNoIGNsdXN0ZXIgYXJlIGVucmljaGVkIGZvciBvbmUgb2Ygb3VyIGNsdXN0LklEcz8KCgpUZXN0OiB3aGF0IHBlcmNlbnRhZ2Ugb2YgZWFjaCBuZXcgY2x1c3RlcklEIG1hdGNoZXMgb25lIG9mIHRoZSBvbGRlciBjbHVzdGVycz8KYGBge3J9CmZvciAobWV0YS5jb2wgaW4gY29sbmFtZXMoY21wLm9iamVjdEBtZXRhLmRhdGEpKXsKCWlmKGdyZXBsKHBhdHRlcm4gPSAoIlJOQV9zbm5fcmVzIiksIHggPSBtZXRhLmNvbCk9PVRSVUUpewoJCW5ldy5jbHVzdGVycyA8LSBzb3J0KGFzLm51bWVyaWMobGV2ZWxzKGNtcC5vYmplY3RAbWV0YS5kYXRhW1ttZXRhLmNvbF1dKSkpCgkJZW5yaWNoLmRmIDwtIGRhdGEuZnJhbWUobWF0cml4KG5jb2wgPSA0LCBucm93ID0gbGVuZ3RoKG5ldy5jbHVzdGVycykpKQoJCWNvbG5hbWVzKGVucmljaC5kZikgPC0gYygzLCAxNywgMTAsIDExKQoJCXJvd25hbWVzKGVucmljaC5kZikgPC0gbmV3LmNsdXN0ZXJzCgkJbWV0YS5kZiA8LSBjbXAub2JqZWN0QG1ldGEuZGF0YQoJCWZvcihyb3cuaWQgaW4gcm93bmFtZXMoZW5yaWNoLmRmKSl7CgkJCXRvdC5jbHVzIDwtIG5yb3cobWV0YS5kZlttZXRhLmRmW1ttZXRhLmNvbF1dID09IHJvdy5pZCxdKQoJCQlmb3IoY29sLmlkIGluIGNvbG5hbWVzKGVucmljaC5kZikpewoJCQkJbnVtLnggPC0gbnJvdyhtZXRhLmRmWyhtZXRhLmRmW1ttZXRhLmNvbF1dID09IHJvdy5pZCkgJiAobWV0YS5kZiRjbHVzdC5JRCA9PSBjb2wuaWQpLF0pCgkJCQlwY3QueCA8LSBhcy5pbnRlZ2VyKG51bS54IC8gdG90LmNsdXMgKjEwMCkKCQkJCSMgcHJpbnQocGN0LngpCgkJCQllbnJpY2guZGZbcm93LmlkLCBjb2wuaWRdIDwtIHBjdC54CgkJCX0KCQl9CgkJY29sbmFtZXMoZW5yaWNoLmRmKSA8LSBzYXBwbHkoY29sbmFtZXMoZW5yaWNoLmRmKSwgZnVuY3Rpb24oeCkgcGFzdGUwKCJvbGRjbHVzdGVyIiwgeCkpCgkJcm93bmFtZXMoZW5yaWNoLmRmKSA8LSBzYXBwbHkocm93bmFtZXMoZW5yaWNoLmRmKSwgZnVuY3Rpb24oeCkgcGFzdGUwKCJuZXdjbHVzdGVyIiwgeCkpCgkJeGxzeDo6d3JpdGUueGxzeChlbnJpY2guZGYsIGZpbGUgPSBwYXN0ZTAoIlBjdE9mTmV3Q2x1c3RlcnNPdmVybGFwcGluZ09sZENsdXN0ZXJzXyIsIHByb2plY3ROYW1lLCAiX2RpbSIsIG5kaW1zLCAiLnhsc3giKSwgc2hlZXROYW1lID0gcGFzdGUwKGdzdWIoIlJOQV9zbm5fIiwgIiIsIG1ldGEuY29sKSksIGFwcGVuZCA9IFRSVUUpCgkJcHJpbnQoZW5yaWNoLmRmKQoJfQp9CgpgYGAKQWJzb2x1dGVseSB0ZXJyaWJsZSBvdmVybGFwLCBubyBlbnJpY2htZW50IG9mIGFueSBvZiB0aGVzZSBhY3Jvc3MgdGhlIG5ldyBjbHVzdGVyaW5nIGFsZ29yaXRobS4gTWF5YmUgc2hvdWxkIHRyeSA5NSUgdmFyaWF0aW9uIGNvdmVyZWQKCiMjIEZpbmQgb2xkIGNlbGxzIG9uIFVNQVAKCnRpbWUgZm9yIHRoZSBzdXBlciBzY2FyZXkgbW9tZW50IHRvIHNlZSBpZiB0aGUgY2VsbHMgZnJvbSBzZXVyYXR2MSBzdGlsbCBjbHVzdGVyIHRvZ2V0aGVyIG9uIGluIHNldXJhdCB2NAoKYGBge3IgZmlnLndpZHRoID0gMn0KRGltUGxvdChjbXAub2JqZWN0LAoJCQkJcmVkdWN0aW9uID0gInVtYXAiLAoJCQkJZ3JvdXAuYnkgPSAiY2x1c3QuSUQiLCAKCQkJCXB0LnNpemUgPSAuMSwKCQkJCSMgc3BsaXQuYnkgPSAib3JpZy5pZGVudCIsCgkJCQljb2xzID0gYygiZ3JheSIsICJvcmFuZ2UiLCAiYmx1ZSIsICJyZWQiLCAiZ3JlZW4iKSwpCmBgYApgYGB7ciBmaWcud2lkdGggPSA0fQpEaW1QbG90KGNtcC5vYmplY3QsCgkJCQlyZWR1Y3Rpb24gPSAidW1hcCIsCgkJCQlncm91cC5ieSA9ICJvcmlnLmlkZW50IiwgCgkJCQlzcGxpdC5ieSA9ICJjbHVzdC5JRCIsCgkJCQljb2xzID0gYygiZ3JheSIsICJvcmFuZ2UiLCAiYmx1ZSIsICJyZWQiLCAiZ3JlZW4iKSwpCmBgYAoKCgojIyMgR2VuZSBleHByZXNzaW9uIG9mIG9sZCBjbHVzdHJzIG9uIG5ldyBtYXAKTGV0J3Mgc2VlIGlmIHdlIGNhbiBnZXQgc29tZSBnZW5lIGV4cHJlc3Npb24gcHJvZmlsZXMgb24gdGhlc2UuLi4KYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xOH0KZ2VuZS5saXN0IDwtIGMoIkdhdGExIiwgIkdhdGEyIiwgIlBmNCIsICJEbnR0IiwgIk1wbyIsICJNZWlzMSIsICJJcmY4IiwgIkVsYW5lIiwgIkZsaTEiLCAiWmZwbTEiKQpWbG5QbG90KGNtcC5vYmplY3QsIGZlYXR1cmVzID0gZ2VuZS5saXN0LCBncm91cC5ieSA9ICJjbHVzdC5JRCIsIHB0LnNpemUgPSAwLjAxLCBjb2xzID0gYygiZ3JheSIsICJvcmFuZ2UiLCAiYmx1ZSIsICJyZWQiLCAiZ3JlZW4iKSkKYGBgCgoKVXNlZCB0aGUgZXhjZSBkb2MgdG8gZG8gc29tZSBmYW5jeSBjb25kaXRpb25hbCBmb3JtYXR0aW5nLiBPbGQgY2x1c3RlciAxNyBpcyBwcmV0dHkgZGlzcGVyc2VkIHVudGlsIHlvdSBpdCByZXNvbHV0aW9uIDIuNS4gT3RoZXJpc2UsIGNlbGxzIGluIG9sZCBjbHVzdGVyIDE3IGRvIG5vdCBjb25zdGl0dXRlIG1vcmUgdGhhbiA0MCUgb2YgYW55IGNlbGxzIGluIHRoZSBuZXcgY2x1c3RlcnMuICAKQXMgZmFyIGFzIEkgY2FuIHNlZSwgdGhlIHR3byBhcHByb2FjaGVzIGFyZSB0byBkbyBER0VvZiBuZXcgQ01QIHcvIHJlc29sdXRpb24gPSAyLjUsIEFORC9PUiBkbyBER2UgdXNpbmcgb2xkZXIgY2x1c3RlciBJRHMuIFN1cmUgc2VlbXMgdG8gbWFrZSBzZW5zZSB0byBkbyBib3RoLi4uCgoKIyBER0Ugdy8gcmVzb2x1dGlvbiA9IDIuNQpTdHJ0IHdpdGggY29tcGFyaW5nIGFsbCBjbHVzdGVycyBhZ2FpbnN0IGFsbCBvdGhlciBjbHVzdGVycwpXcml0ZSBvdXQgY2x1c3RlciBpbmZvCgoKY2FsY3VsYXRlIGBGaW5kQWxsTWFya2VycygpYCBmb3IgZGlmZmVyZW50IGlkZW50cyBhbmQgc2F2ZSB0byBuZXcgZmlsZQpgYGB7cn0KaWRlbnQubGlzdCA8LSBjKCJSTkFfc25uX3Jlcy4wLjUiLCAiUk5BX3Nubl9yZXMuMSIsICJSTkFfc25uX3Jlcy4xLjUiLCAiUk5BX3Nubl9yZXMuMiIsICJSTkFfc25uX3Jlcy4yLjUiLCAiY2x1c3QuSUQiKQpmb3IodGVzdGVkLmlkZW50IGluIGlkZW50Lmxpc3QpewoJSWRlbnRzKGNtcC5vYmplY3QpIDwtIHRlc3RlZC5pZGVudAoJYWxsLm1hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMoY21wLm9iamVjdCkKCXhsc3g6OndyaXRlLnhsc3goeCA9IGFsbC5tYXJrZXJzWyxjKCJhdmdfbG9nMkZDIiwgInBfdmFsX2FkaiIsICJjbHVzdGVyIiwgImdlbmUiKV0sIAoJCQkJCQkJCQkgZmlsZSA9IHBhc3RlMChwcm9qZWN0TmFtZSwgIl9GaW5kQUxMTWFya2Vyc19yZXMyLjUueGxzeCIpLCAKCQkJCQkJCQkJIHNoZWV0TmFtZSA9IHRlc3RlZC5pZGVudCwgCgkJCQkJCQkJCSBjb2wubmFtZXMgPSBUUlVFLCAKCQkJCQkJCQkJIHJvdy5uYW1lcyA9IEZBTFNFLCAKCQkJCQkJCQkJIGFwcGVuZCA9IFRSVUUpCn0KYGBgCgpDcmVhdGUgYEZpbmRBbGxNYXJrZXJzKClgIGxpc3RzIGZvciBHU0VBCmBgYHtyfQpJZGVudHMoY21wLm9iamVjdCkgPC0gIlJOQV9zbm5fcmVzLjIuNSIKcmVzLjIuNS5hbGxtYXJrZXJzIDwtIEZpbmRBbGxNYXJrZXJzKGNtcC5vYmplY3QpCmBgYAoKTWFwIEhHTkMgc3ltYm9scwpgYGB7cn0KTW91c2UySHVtYW5UYWJsZSA8LSBNb3VzZTJIdW1hbihyZXMuMi41LmFsbG1hcmtlcnMkZ2VuZSkKCkhHTkMgPC0gd2l0aChNb3VzZTJIdW1hblRhYmxlLCBNb3VzZTJIdW1hblRhYmxlJEhHTkNbbWF0Y2gocmVzLjIuNS5hbGxtYXJrZXJzJGdlbmUsIE1vdXNlMkh1bWFuVGFibGUkTUdJKV0pCmhlYWQocmVzLjIuNS5hbGxtYXJrZXJzKQpyZXMuMi41LmFsbG1hcmtlcnMkSEdOQyA8LSBIR05DCnRhaWwocmVzLjIuNS5hbGxtYXJrZXJzKQpzaWcucmVzLjIuNSA8LSByZXMuMi41LmFsbG1hcmtlcnNbcmVzLjIuNS5hbGxtYXJrZXJzJHBfdmFsX2FkaiA8PSAwLjA1LCBdCnNpZy5yZXMuMi41IDwtIHNpZy5yZXMuMi41W2MoImF2Z19sb2cyRkMiLCAiSEdOQyIsICJjbHVzdGVyIildCnNpZy5yZXMuMi41IDwtIHNpZy5yZXMuMi41WyEoc2lnLnJlcy4yLjUkSEdOQyA9PSAiIiB8IGlzLm5hKHNpZy5yZXMuMi41JEhHTkMpKSxdICMgR1NFQSB3aWxsIGZhaWwgaWYgdGhlcmUgYXJlIGFueSBibGFua3Mgb3IgTkFzIGluIHRoZSB0YWJsZQpzaWcucmVzLjIuNSA8LSBzaWcucmVzLjIuNVtdCgpgYGAKCgpgYGB7cn0KZm9yKGNsdXN0ZXIgaW4gdW5pcXVlKHNpZy5yZXMuMi41JGNsdXN0ZXIpKXsKCXByaW50KHBhc3RlKCJ3cml0aW5nIGNsdXN0ZXIiLCBjbHVzdGVyKSkKCW5ldy50YWJsZSA8LSBzaWcucmVzLjIuNVtzaWcucmVzLjIuNSRjbHVzdGVyID09IGNsdXN0ZXIsIGMoIkhHTkMiLCAiYXZnX2xvZzJGQyIpXQoJbmV3LnRhYmxlIDwtIG5ldy50YWJsZVtvcmRlcigtbmV3LnRhYmxlJGF2Z19sb2cyRkMpLCBdCgl3cml0ZS50YWJsZShuZXcudGFibGUsIGZpbGUgPSBwYXN0ZTAoIlJhbmtMaXN0X3JlczIuNV9maW5kQWxsX2hnbmMvcmVzLjIuNWNsdXN0ZXIiLCBjbHVzdGVyLCAiLnJuayIpLCBxdW90ZSA9IEZBTFNFLCByb3cubmFtZXMgPSBGQUxTRSwgY29sLm5hbWVzID0gVFJVRSwgc2VwID0gIlx0IiwgKQoJCn0KYGBgCgoKCmNhbGN1bGF0ZSBgRmluZE1hcmtlcnMoKWAgdGhhdCBkaXN0aW5ndWlzaCBlYWNoIGNsdXN0ZXIgKG1pZ2h0IG92ZXJsYWIgYmV0d2VlbiBjbHVzdGVycykKYGBge3J9CmlkZW50Lmxpc3QgPC0gYygiUk5BX3Nubl9yZXMuMC41IiwgIlJOQV9zbm5fcmVzLjEiLCAiUk5BX3Nubl9yZXMuMS41IiwgIlJOQV9zbm5fcmVzLjIiLCAiUk5BX3Nubl9yZXMuMi41IiwgImNsdXN0LklEIikKZm9yKHRlc3RlZC5pZGVudCBpbiBpZGVudC5saXN0KXsKCWZvciAoY2x1c3RlciBpbiBzb3J0KGFzLm51bWVyaWMobGV2ZWxzKGNtcC5vYmplY3RAbWV0YS5kYXRhW1t0ZXN0ZWQuaWRlbnRdXSkpKSl7CgljbHVzdGVyLm1hcmtlcnMgPC0gRmluZE1hcmtlcnMoY21wLm9iamVjdCwgaWRlbnQuMSA9IGNsdXN0ZXIpCgl4bHN4Ojp3cml0ZS54bHN4KHggPSBjbHVzdGVyLm1hcmtlcnNbLGMoImF2Z19sb2cyRkMiLCAicF92YWxfYWRqIildLCAKCQkJCQkJCQkJIGZpbGUgPSBwYXN0ZTAocHJvamVjdE5hbWUsICJfRmluZE1hcmtlcnNfIiwgZ3N1YigiUk5BX3Nubl8iLCAiIiwgdGVzdGVkLmlkZW50KSwgIi54bHN4IiksIAoJCQkJCQkJCQkgc2hlZXROYW1lID0gcGFzdGUwKCJjbHN0IiwgY2x1c3RlciksIAoJCQkJCQkJCQkgY29sLm5hbWVzID0gVFJVRSwgCgkJCQkJCQkJCSByb3cubmFtZXMgPSBUUlVFLCAKCQkJCQkJCQkJIGFwcGVuZCA9IFRSVUUpCn0KfQpgYGAKCgoKYGBge3J9CmZvciAoY2x1c3RlciBpbiBzb3J0KGFzLm51bWVyaWMobGV2ZWxzKGNtcC5vYmplY3RAbWV0YS5kYXRhJFJOQV9zbm5fcmVzLjIuNSkpKSl7CgljbHVzdGVyLm1hcmtlcnMgPC0gRmluZE1hcmtlcnMoY21wLm9iamVjdCwgaWRlbnQuMSA9IGNsdXN0ZXIpCgl4bHN4Ojp3cml0ZS54bHN4KHggPSBjbHVzdGVyLm1hcmtlcnNbLGMoImF2Z19sb2cyRkMiLCAicF92YWxfYWRqIildLCAKCQkJCQkJCQkJIGZpbGUgPSBwYXN0ZTAocHJvamVjdE5hbWUsICJfRmluZE1hcmtlcnNfcmVzMi41Lnhsc3giKSwgCgkJCQkJCQkJCSBzaGVldE5hbWUgPSBwYXN0ZTAoImNsc3QiLCBjbHVzdGVyKSwgCgkJCQkJCQkJCSBjb2wubmFtZXMgPSBUUlVFLCAKCQkJCQkJCQkJIHJvdy5uYW1lcyA9IFRSVUUsIAoJCQkJCQkJCQkgYXBwZW5kID0gVFJVRSkKfQpgYGAKCiMjIENvbWJpbmUgY2x1c3RlcnMgdGhhdCBtaWdodCByZXByZXNlbnQgb2xkIGNsdXN0ZXIgaWRzCgojIERHRSB3LyBtZXRhZGF0YSBhZ2FpbnN0IGNsdXN0LklEIGFnYWluc3QgIjAiCnJlc2V0IGlkZW50IGFzICJjbHVzdC5JRCIgYW5kIHJlcnVuIGBGaW5kQWxsTWFya2VycygpYApgYGB7cn0KCUlkZW50cyhjbXAub2JqZWN0KSA8LSAiY2x1c3QuSUQiCglhbGwubWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhjbXAub2JqZWN0KQoJeGxzeDo6d3JpdGUueGxzeCh4ID0gYWxsLm1hcmtlcnNbLGMoImF2Z19sb2cyRkMiLCAicF92YWxfYWRqIiwgImNsdXN0ZXIiLCAiZ2VuZSIpXSwgCgkJCQkJCQkJCSBmaWxlID0gcGFzdGUwKHByb2plY3ROYW1lLCAiX0ZpbmRBTExNYXJrZXJzX2NsdXN0SUQueGxzeCIpLCAKCQkJCQkJCQkJIHNoZWV0TmFtZSA9ICJjbHVzdElEIiwgCgkJCQkJCQkJCSBjb2wubmFtZXMgPSBUUlVFLCAKCQkJCQkJCQkJIHJvdy5uYW1lcyA9IEZBTFNFLCAKCQkJCQkJCQkJIGFwcGVuZCA9IFRSVUUpCmBgYAoKCmBgYHtyfQojIElkZW50cyhjbXAub2JqZWN0KSA8LSAiY2x1c3QuSUQiCmZvciAoY2x1c3RlciBpbiB1bmlxdWUoY21wLm9iamVjdEBtZXRhLmRhdGEkY2x1c3QuSUQpKXsKCXByaW50KGNsdXN0ZXIpCgljbHVzdGVyLm1hcmtlcnMgPC0gRmluZE1hcmtlcnMoY21wLm9iamVjdCwgaWRlbnQuMSA9IGNsdXN0ZXIpCgl4bHN4Ojp3cml0ZS54bHN4KHggPSBjbHVzdGVyLm1hcmtlcnNbLGMoImF2Z19sb2cyRkMiLCAicF92YWxfYWRqIildLCAKCQkJCQkJCQkJIGZpbGUgPSBwYXN0ZTAocHJvamVjdE5hbWUsICJfRmluZE1hcmtlcnNfY2x1c3RJRC54bHN4IiksIAoJCQkJCQkJCQkgc2hlZXROYW1lID0gcGFzdGUwKCJvbGRjbHVzdCIsIGNsdXN0ZXIpLCAKCQkJCQkJCQkJIGNvbC5uYW1lcyA9IFRSVUUsIAoJCQkJCQkJCQkgcm93Lm5hbWVzID0gVFJVRSwgCgkJCQkJCQkJCSBhcHBlbmQgPSBUUlVFKQp9CgpgYGAKCgojIERpc3Rpbmd1aXNoaW5nIGZlYXR1cmVzIG9mIGNsdXN0ZXJzClByZXZpb3VzbHkgZGVmaW5lZCBiaW9tYXJrIGdlbmVzIGJhc2VkIG9uIFBDIGNvbnRyaWJ1dGlvbnMuIE9yaWdpbmFsIGxpc3Qgd2FzIGJhc2VkIG9uICphbGwqIG1zQWdnciwgYnV0IGxldCdzIHNlZSBob3cgQ01QIHN1YnNldCBkb2VzPwpgYGB7ciBmaWcuaGVpZ2h0ID0gMzAsIGZpZy53aWR0aD02fQpWaXpEaW1Mb2FkaW5ncyhjbXAub2JqZWN0LCBkaW1zID0gMToxMCwgbmZlYXR1cmVzID0gMzAsIHJlZHVjdGlvbiA9ICJwY2EiLCBuY29sID0gMikKYGBgCgpgYGB7cn0KcGNhLmRmIDwtIGNtcC5vYmplY3RbWyJwY2EiXV0KcGNhLmRmIDwtIGFzLmRhdGEuZnJhbWUoYXMubWF0cml4KHNsb3Qob2JqZWN0ID0gcGNhLmRmLCBuYW1lID0gImZlYXR1cmUubG9hZGluZ3MiKSkpCnByaW50KGNtcC5vYmplY3RbWyJwY2EiXV0sIGRpbXMgPSAyLCBuZmVhdHVyZXMgPSA1KQpyb3duYW1lcyhwY2EuZGZbcGNhLmRmJFBDXzIgJWluJSBzb3J0KHBjYS5kZiRQQ18yLCBkZWNyZWFzaW5nID0gVFJVRSlbMTo1XSwgXSkKcm93bmFtZXMocGNhLmRmW3BjYS5kZiRQQ18yICVpbiUgc29ydChwY2EuZGYkUENfMilbMTo1XSwgXSkKYGBgCgpub3cgd2UgY2FuIGdldCBhIGxpc3Qgb2YgcHJpbmNpcGFsIGNvbXBvbmVudHMhICAKZmlyc3QgcHVsbCB0aGUgbGlzdCBvZiBvbGRBbmFseXNpcyBDTVAgdG9wIFBDIGdlbmVzCmBgYHtyfQpjbXAuYmlvbWFyayA8LSByZWFkLnRhYmxlKGZpbGUgPSAiL1VzZXJzL2hldXN0b25lZi9EZXNrdG9wL0NNUFN1YnBvcHMvQmlvTWFyay9Qcm9iZVBhbmVscy9DTVBfUENUb3BHZW5lcy50eHQiLCBzZXAgPSAiXHQiLCBoZWFkZXIgPSBUUlVFKQpiaW9tYXJrLmNtcHRhcmdldHMgPC0gYygpCmZvcihkZi5jb2wgaW4gMTpuY29sKGNtcC5iaW9tYXJrKSl7CgliaW9tYXJrLmNtcHRhcmdldHMgPC0gYyhiaW9tYXJrLmNtcHRhcmdldHMsIGJpb21hcmtbLGRmLmNvbF0pCn0KcHJpbnQoY29sbmFtZXMoYmlvbWFyaykpCnByaW50KHBhc3RlKCJ0b3RhbCBnZW5lIGNvdW50OiIsIGxlbmd0aChiaW9tYXJrLmNtcHRhcmdldHMpKSkKYGBgCgpOb3cgZ2V0IHRoZSBsaXN0IG9mIGN1cnJlbnQgcGMgZ2VuZSB0cmdldHMgKG9sZEFuYWx5c2lzIHVzZWQgbmRpbSA9IDE6Niwgc28gd2UnbGwgc3RhcnQgd2l0aCB0aGF0IHJhbmdlKQpgYGB7cn0KcGMubGlzdCA8LSBjKCJQQ18xIiwgIlBDXzIiLCAiUENfMyIsICJQQ180IiwgIlBDXzUiLCAiUENfNiIpCnBjLmdlbmVzIDwtIGxhcHBseShwYy5saXN0LCBmdW5jdGlvbih4KSByb3duYW1lcyhwY2EuZGZbcGNhLmRmW1t4XV0gJWluJSBzb3J0KHBjYS5kZltbeF1dLCBkZWNyZWFzaW5nID0gVFJVRSlbMTozMF0sXSkpICN0YXJnZXRpbmcgcm91Z2hseSAxODAgZ2VuZXMgbGlrZSBpbiBiaW9tYXJrLmNtcHRhcmdldHMKcGMuZ2VuZXMgPC0gdW5pcXVlKHVubGlzdChwYy5nZW5lcykpCnByaW50KHBhc3RlKCJ0b3RhbCBnZW5lIGNvdW50OiIsIGxlbmd0aChwYy5nZW5lcykpKQpgYGAKCk5vdyBjb21wYXJlIHRoZSBsaXN0cywgSSBndWVzczoKCmBgYHtyfQojIHNldGRpZmYoeCx5KSBnaXZlcyB5b3UgdGhpbmdzIGluIHggbm90IGluIHkuIHNldGRpZmYoeSx4KSBnaXZlcyB5b3UgdGhpbmdzIGluIHkgbm90IGluIHgKc2V0ZGlmZihiaW9tYXJrLmNtcHRhcmdldHMsIHBjLmdlbmVzKQojIHByaW50KHBhc3RlKCJcbiBsZW5ndGg6IiwgbGVuZ3RoKHNldGRpZmYoYmlvbWFyay5jbXB0YXJnZXRzLCBwYy5nZW5lcykpKSkKd3JpdGVMaW5lcyhjKCIiLCAibGVuZ3RoOiIsIGxlbmd0aChzZXRkaWZmKGJpb21hcmsuY21wdGFyZ2V0cywgcGMuZ2VuZXMpKSkpCmBgYApVbW0sIHllYWggdGhhdCB3ZW50IGtpbmRhIGhvdyBJIGV4cGVjdGVkLiBMZXQncyBkbyB0aGlzIGFnYWluLCBidXQgZm9yIHRoZSBhY3R1YWwgYmlvbWFyayBnZW5lIGxpc3RzLgpgYGB7cn0KYmlvbWFyayA8LSByZWFkLnRhYmxlKGZpbGUgPSAiL1VzZXJzL2hldXN0b25lZi9EZXNrdG9wL0NNUFN1YnBvcHMvQmlvTWFyay9Qcm9iZVBhbmVscy9CaW9tYXJrUHJvYmVMaXN0LnR4dCIsIHNlcCA9ICJcdCIpCmJpb21hcmsgPC0gYmlvbWFya1ssMV0Kc2V0ZGlmZihiaW9tYXJrLCBwYy5nZW5lcykKd3JpdGVMaW5lcyhjKCIiLCAibGVuZ3RoOiIsIGxlbmd0aChzZXRkaWZmKGJpb21hcmssIHBjLmdlbmVzKSkpKQpgYGAKCgpXaGF0IGlmIHdlIGluY3JlYXNlIHRoZSBudW1iZXIgb2YgcGNzIGJ1dCBkZWNyZWFzZSB0aGUgZGVwdGggb2YgZWFjaD8gVGhpcyBtaWdodCBjb3ZlciBtb3JlIG9mIGBiaW9tYXJrCWAsIHdoaWNoIHdhcyBvcmlnaW5hbGx5IGRldmVsb3BlZCB1c2luZyBtc0FnZ3IgaW5zdGVhZCBvZiBvbmx5IHRoZSBDTVAgc3Vic2V0CmBgYHtyfQpwYy5saXN0IDwtIGMoIlBDXzEiLCAiUENfMiIsICJQQ18zIiwgIlBDXzQiLCAiUENfNSIsICJQQ182IiwgIlBDXzciLCAiUENfOCIsICJQQ185IiwgIlBDXzEwIikKcGMuZ2VuZXMgPC0gbGFwcGx5KHBjLmxpc3QsIGZ1bmN0aW9uKHgpIHJvd25hbWVzKHBjYS5kZltwY2EuZGZbW3hdXSAlaW4lIHNvcnQocGNhLmRmW1t4XV0sIGRlY3JlYXNpbmcgPSBUUlVFKVsxOjIwXSxdKSkKcGMuZ2VuZXMgPC0gdW5pcXVlKHVubGlzdChwYy5nZW5lcykpCnByaW50KHBhc3RlKCJ0b3RhbCBnZW5lIGNvdW50OiIsIGxlbmd0aChwYy5nZW5lcykpKQoKYGBgCmBgYHtyfQpzZXRkaWZmKGJpb21hcmssIHBjLmdlbmVzKQp3cml0ZUxpbmVzKGMoIiIsICJsZW5ndGg6IiwgbGVuZ3RoKHNldGRpZmYoYmlvbWFyaywgcGMuZ2VuZXMpKSkpCmBgYAoKRm9yIGNvbXBhcmlzb24sIGxldCdzIGp1c3Qgc2VlIGhvdyBtYW55IG9mIGBiaW9tYXJrLmNtcHRhcmdldHNgIHdlcmUgYWN0dWFsbHkgaW5jbHVkZWQgaW4gYGJpb21hcmtgCmBgYHtyfQpzZXRkaWZmKGJpb21hcmsuY21wdGFyZ2V0cywgYmlvbWFyaykKd3JpdGVMaW5lcyhjKCIiLCAibGVuZ3RoOiIsIGxlbmd0aChzZXRkaWZmKGJpb21hcmsuY21wdGFyZ2V0cywgcGMuZ2VuZXMpKSkpCmBgYApgYGB7cn0KbGVuZ3RoKGJpb21hcmspIC0gbGVuZ3RoKHNldGRpZmYoYmlvbWFyaywgYmlvbWFyay5jbXB0YXJnZXRzKSkKYGBgCmBgYHtyfQpsZW5ndGgoYmlvbWFyaykgLSBsZW5ndGgoc2V0ZGlmZihiaW9tYXJrLCBwYy5nZW5lcykpCmBgYApTbyB3aGVuIHlvdSBsb29rIGF0IGl0IGxpa2UgdGhhdCwgaXQncyBub3QgYWN0dWFsbHkgdGhhdCBmYXIgb2ZmLgoKCldoYXQgYXJlIHRoZSBzaW1pbGFyaXRpZXM/OgpgYGB7cn0Kc2V0ZGlmZihzZXRkaWZmKGJpb21hcmssIGJpb21hcmsuY21wdGFyZ2V0cyksIHNldGRpZmYoYmlvbWFyaywgcGMuZ2VuZXMpKQpgYGAKVGhlc2UgYXJlIGdlbmVzIGZyb20gdGhlIDk3cHJvYmVzIG5vdCBpbiB0aGUgb2xkIENNUCBzZXQgdGhhdCBhcmUgYWxzbyBub3QgaW4gdGhlIG5ldyBDTVAgc2V0LiBPdGhlciB0aGFuIEl0Z2EyYiAod2hpY2ggaXMgYSBmYWlsZWQgcHJvYmUgYW55d2F5KSwgbm90aGluZyBzY3JlYW1zLiBBbHNvIHdlJ2QgaGF2ZSB0aHJvd24gRmx0MyBhbmQgQ2QzNCBmb3IgaW4gYW55d2F5IGJlY2F1c2UgdGhleSdyZSByZXF1aXNpdGUgY2VsbCBzdXJmYWNlIG1hcmtlcnMgKGFsc28gRmx0MyBzdXJmYWNlIG1hcmtlciBpcyBleHBlbnNpdmUgYnV0IG90aGVyd2lzZSBub3Qgbm90ZXdvcnRoeSBhbmQgbm90IHVzZWQgaW4gdGhlIGN1cnJlbnQgc29ydGluZyBzdHJhdGVneSkKCldoYXQgYWJvdXQgY2VsbCBzdXJmYWNlIG1hcmtlciBleHByZXNzaW9uPwoqIENkMzQKKiBDZDE2LzMyCiogQ2Q5CiogQ2Q0MQoqIENkNDgKKiBTY2ExIChqdXN0IHRocm93IHRoYXQgaW4gZm9yIHNoKiZzIGFuZCBnaWdnbGVzKQpgYGB7ciwgZmlnLmhlaWdodCA9IDE1LCBmaWcud2lkdGg9MTB9CnN1cmZhY2UubWFya2VycyA8LSBjKCJDZDM0IiwgIkZjZ3IzIiwgIkZjZ3IyYiIsICJDZDkiLCAiSXRnYTJiIiwgIkNkNDgiLCAiTHk2YSIpCkZlYXR1cmVQbG90KGNtcC5vYmplY3QsIGZlYXR1cmVzID0gc3VyZmFjZS5tYXJrZXJzLCBwdC5zaXplID0gMSwgc3BsaXQuYnkgPSAiY2x1c3QuSUQiLCBuY29sID0gMSkKYGBgClNhdmUgYXMgcG5nCmBgYHtyfQpwbmcoZmlsZW5hbWUgPSAiRmVhdHVyZVBsb3RfQ01QX3N1cmZhY2VNYXJrZXJzX2NsdXN0SURmYWNldC5wbmciLCBoZWlnaHQgPSAxNjAwLCB3aWR0aCA9IDE2MDApCkZlYXR1cmVQbG90KGNtcC5vYmplY3QsIGZlYXR1cmVzID0gc3VyZmFjZS5tYXJrZXJzLCBwdC5zaXplID0gMSwgc3BsaXQuYnkgPSAiY2x1c3QuSUQiLCBuY29sID0gMSkKZGV2Lm9mZigpCmBgYAoKCgoKCiMgQ2VsbCBjeWNsZSBhbmFseXNpcwpKdXN0IHNvIHdlIGtub3cgd2hhdCB3ZSdyZSB3b3JraW5nIHdpdGgKCgpgYGB7cn0Kcy5nZW5lcyA8LSBjYy5nZW5lcy51cGRhdGVkLjIwMTkkcy5nZW5lcwpnMm0uZ2VuZXMgPC0gY2MuZ2VuZXMudXBkYXRlZC4yMDE5JGcybS5nZW5lcwpgYGAKCgojIENvbXBhcmUgQCBoaWVyYXJjaGNpYWwgY2x1c3RlaXJuZwoKRG8gY2x1c3RlcmluZyB1c2luZyBiaW9tYXJrIFJOQXMgYXMgaW5wdXQKYGBge3J9CiMgUmVhZCBpbiBCaW9tYXJrUk5BcwpiaW9tYXJrLnJuYXMgPC0gcmVhZC50YWJsZSgnL1VzZXJzL2hldXN0b25lZi9EZXNrdG9wLzEwWEdlbm9taWNzRGF0YS9CaW9tYXJrUk5Bcy50eHQnKQpiaW9tYXJrLnJuYXMgPC0gYmlvbWFyay5ybmFzJFYxCmBgYAoKdXNlIGJpb21hcmsgUk5BcyB0byBkZWZpbmUgZGltZW5zaW9uYWwgcmVkdWN0aW9uCmBgYHtyfQpjbXAub2JqZWN0IDwtIHJlYWRSRFMoIkNNUF9yYXcuUkRTIikKY21wLm9iamVjdCA8LSBSdW5QQ0EoY21wLm9iamVjdCwgZmVhdHVyZXMgPSBiaW9tYXJrLnJuYXMsIG5kaW1zLnByaW50ID0gMTo1LCAsIG5mZWF0dXJlcy5wcmludCA9IDUpCkVsYm93UGxvdChjbXAub2JqZWN0LCBuZGltcyA9IDUwKQpgYGAKCgpOb3cgcnVuIHRoZSBjbHVzdGVyaW5nCmBgYHtyfQp0b3QudmFyIDwtIHBlcmNlbnQudmFyaWFuY2UoY21wLm9iamVjdEByZWR1Y3Rpb25zJHBjYUBzdGRldiwgcGxvdC52YXIgPSBGQUxTRSwgcmV0dXJuLnZhbCA9IFRSVUUpCm5kaW1zIDwtIGxlbmd0aCh3aGljaChjdW1zdW0odG90LnZhcikgPD0gOTApKQoKY21wLm9iamVjdCA8LSBGaW5kTmVpZ2hib3JzKGNtcC5vYmplY3QsIGRpbXMgPSAxOm5kaW1zKQpjbXAub2JqZWN0IDwtIEZpbmRDbHVzdGVycyhjbXAub2JqZWN0LCByZXNvbHV0aW9uID0gMC41KQpjbXAub2JqZWN0IDwtIFJ1blVNQVAoY21wLm9iamVjdCwgZGltcyA9IDE6IG5kaW1zKQoKCmBgYAoKZmluZCB0aGUgY2x1c3RlcnMKCmBgYHtyfQpmb3IoeCBpbiBjKDAuNSwgMSwgMS41LCAyLCAyLjUpKXsKCWNtcC5vYmplY3QgPC0gRmluZENsdXN0ZXJzKGNtcC5vYmplY3QsIHJlc29sdXRpb24gPSB4KQp9CmBgYAoKUGxvdCB0aGUgdW1hcHMgYW5kIGNlbGwgY2x1c3RlciBpZHMKYGBge3J9CmZvciAobWV0YS5jb2wgaW4gY29sbmFtZXMoY21wLm9iamVjdEBtZXRhLmRhdGEpKXsKCWlmKGdyZXBsKHBhdHRlcm4gPSAoIlJOQV9zbm5fcmVzIiksIHggPSBtZXRhLmNvbCk9PVRSVUUpewoJCW15cGxvdCA8LSBEaW1QbG90KGNtcC5vYmplY3QsIAoJCQkJCQkJCQkJCWdyb3VwLmJ5ID0gbWV0YS5jb2wsCgkJCQkJCQkJCQkJcmVkdWN0aW9uID0gInVtYXAiLCAKCQkJCQkJCQkJCQljb2xzID0gY29sb3IucGFsZXR0ZQoJCQkJCQkJCQkJCSkgKyAKCQkJZ2d0aXRsZShwYXN0ZTAocHJvamVjdE5hbWUsICIgZGltIiwgbmRpbXMsICJyZXMiLCBnc3ViKCJSTkFfc25uX3JlcyIsICIiLCBtZXRhLmNvbCkgKSkKCQlwbG90KG15cGxvdCkKCX0KfQpgYGAKIyMjIENhbGN1bGF0ZSBhbnRpY2lwYXRlZCBudW1iZXIgb2YgY2VsbHMgeW91J2xsIGZpbmQgaW4gZWFjaCBiaW9tYXJrIGNsdXN0ZXIKR2V0ICMgY2VsbHMgaW4gZWFjaCBjbHVzdGVyCgpgYGB7cn0KdG90LmNlbGxjb3VudCA8LSBucm93KGNtcC5vYmplY3RAbWV0YS5kYXRhKQpyZXMwNS5saXN0IDwtIHNvcnQodW5pcXVlKGNtcC5vYmplY3RAbWV0YS5kYXRhJFJOQV9zbm5fcmVzLjAuNSksIGRlY3JlYXNpbmcgPSBGQUxTRSkKc2FwcGx5KHJlczA1Lmxpc3QsIAoJCQkgZnVuY3Rpb24oeCl7CgkJCSAJcHJpbnQoCgkJCSAJCXBhc3RlKAoJCQkgCQkJImNsdXN0ZXIiLCB4LCAiPSIsIAoJCQkgCQkJbnJvdyhjbXAub2JqZWN0QG1ldGEuZGF0YVtjbXAub2JqZWN0QG1ldGEuZGF0YSRSTkFfc25uX3Jlcy4wLjUgPT0geCxdKSwgCgkJCSAJCQkiY2VsbHMgb3IiLCAKCQkJIAkJCXJvdW5kKG5yb3coY21wLm9iamVjdEBtZXRhLmRhdGFbY21wLm9iamVjdEBtZXRhLmRhdGEkUk5BX3Nubl9yZXMuMC41ID09IHgsXSkvdG90LmNlbGxjb3VudCoxMDAsIGRpZ2l0cyA9IDIpLCAKCQkJIAkJCSIlIG9mIHRvdGFsIgoJCQkgCQkpCgkJCSAJKQoJCQkgfQoJCQkpCmBgYAoKU28gd2UgZGlkIHRoZSBkaW1lbnNpb25hbCByZWR1Y3Rpb24gYmFzZWQgb24gdGhlIGJpb21hcmsgUk5BcywgdGhlbiBkaWQgb3VyIFVNQVAgbmVhcmVzdCBuZWlnaGJvciBjbHVzdGVyaW5nLgoKCkluIHRoZSBiaW9tYXJrIGhpZXJhcmNoY2lhbCBjbHVzdGVyaW5nIGFuYWx5c2lzIEkgYXNzYXllZCAxNjcgY2VsbHMuIFRoZSBzbWFsbGVzdCBjbHVzdGVyIEkgZGV0ZWN0ZWQgaGFkIDMgY2VsbHMsIG9yIDEuOCUgb2YgdG90YWwsIGFuZCB0aGlzIGlzIGFuIHVuY29tZm9ydGFibHkgc21hbGwgbnVtYmVyIG9mIGNlbGxzLiBCYXNlZCBvbiB0aGUgVU1BUCBjYWxjdWxhdGlvbnMgSSB3b3VsZCB0aGVyZWZvcmUgZXhwZWN0IHRvIGZpbmQgMTEgb3IgMTIgb2YgdGhlIHByZWRpY3RlZCAxNSBjbHVzdGVycy4gSSBmb3VuZCAxMiwgYW5kIEkgZG9uJ3QgcmVhbGx5IGxpa2UgdGhhdCBsYXN0IG9uZSwgc28gMTEgb3IgMTIuIFNpbmNlIEkgZGlkIHRoZSBoaWVyYXJjaGNpYWwgY2x1c3RlcmluZyB5ZXN0ZXJkYXkgYW5kIGRpZCB0aGlzIG1hdGggdG9kYXksIHdlIGNhbiBzYXkgaXQgd2FzIGluZGVwZW5kZW50IG9mIHRoZXNlIHJlc3VsdHMgYW5kIHRoZXJlZm9yZSB0b3RhbGx5IGxlZ2l0LiBZYXkhIQoK